package com.zusmart.core.proxy.support.cglib;

import java.lang.reflect.Method;

import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import org.objenesis.instantiator.ObjectInstantiator;

import com.zusmart.base.util.Assert;
import com.zusmart.base.util.GenericUtils;
import com.zusmart.core.proxy.ProxyHandler;
import com.zusmart.core.proxy.ProxyHandlerForInterface;
import com.zusmart.core.proxy.ProxyMethod;
import com.zusmart.core.proxy.support.AbstractProxyManager;
import com.zusmart.core.proxy.support.ProxyChainForClass;
import com.zusmart.core.proxy.support.ProxyChainForInstance;
import com.zusmart.core.proxy.support.ProxyChainForInterface;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxyManager extends AbstractProxyManager {

	@Override
	public <T> T createProxyByClass(final Class<T> targetClass, final Class<?>[] targetParameterTypes, final Object[] targetParameterDatas, final ProxyHandler... handlers) {
		Assert.isNull(targetClass, "proxy class must not be null");
		Assert.isTrue(null == handlers || handlers.length == 0, "proxy handlers must not be null");
		Assert.isTrue(targetClass.isInterface(), "proxy class must not be interface");
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(targetClass);
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object targetObject, Method targetMethod, Object[] targetParameterDatas, MethodProxy targetMethodProxy) throws Throwable {
				ProxyMethod targetProxyMethod = new CglibProxyMethodByClass(targetMethod, targetMethodProxy);
				return new ProxyChainForClass(targetClass, targetObject, targetParameterDatas, targetProxyMethod, handlers).doProxyChain();
			}
		});
		if (null == targetParameterTypes || targetParameterTypes.length == 0) {
			return GenericUtils.parse(enhancer.create());
		} else {
			return GenericUtils.parse(enhancer.create(targetParameterTypes, targetParameterDatas));
		}
	}

	@Override
	public <T> T createProxyByInstance(final T instance, final ProxyHandler... handlers) {
		Assert.isNull(instance, "proxy instance must not be null");
		Assert.isTrue(null == handlers || handlers.length == 0, "proxy handlers must not be null");
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(instance.getClass());
		enhancer.setCallbackType(MethodInterceptor.class);
		final Class<?> clazz = enhancer.createClass();
		Objenesis objenesis = new ObjenesisStd();
		ObjectInstantiator<?> thingInstantiator = objenesis.getInstantiatorOf(clazz);
		Object proxyInstance = thingInstantiator.newInstance();
		Factory factory = (Factory) proxyInstance;
		factory.setCallback(0, new MethodInterceptor() {
			@Override
			public Object intercept(Object targetObject, Method targetMethod, Object[] targetParameterDatas, MethodProxy targetMethodProxy) throws Throwable {
				ProxyMethod targetProxyMethod = new CglibProxyMethodByInstance(targetMethod, targetMethodProxy);
				return new ProxyChainForInstance(targetObject.getClass(), targetObject, targetParameterDatas, targetProxyMethod, handlers).doProxyChain();
			}
		});
		return GenericUtils.parse(factory);
	}

	@Override
	public <T> T createProxyByInterface(final Class<T> targetClass, final ProxyHandlerForInterface... handlers) {
		Assert.isNull(targetClass, "proxy class must not be null");
		Assert.isTrue(null == handlers || handlers.length == 0, "proxy handlers must not be null");
		Assert.isTrue(!targetClass.isInterface(), "proxy class must not be interface");
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(targetClass);
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object targetObject, Method targetMethod, Object[] targetParameterDatas, MethodProxy targetMethodProxy) throws Throwable {
				ProxyMethod targetProxyMethod = new CglibProxyMethodByInterface(targetMethod, targetMethodProxy);
				return new ProxyChainForInterface(targetClass, targetObject, targetParameterDatas, targetProxyMethod, handlers).doProxyChain();
			}
		});
		return GenericUtils.parse(enhancer.create());
	}

	@Override
	public boolean isProxy(Object targetInstance) {
		return null != targetInstance && targetInstance instanceof Factory;
	}

}